// 2013年版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

package types

import (
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
)

// Eval返回类型，如果为常量，则返回
// 表达式expr，在包装包装的位置处计算，
// 它必须是通过对AST进行类型检查而派生的
// 与提供的文件相关的完整位置信息
// 设置
// None
// 参数fset、pkg和pos的含义是
// 与CheckExpr中的相同。如果expr无法执行，则返回错误
// 已成功解析，或者无法解析结果表达式
// 类型已检查。
func Eval(fset *token.FileSet, pkg *Package, pos token.Pos, expr string) (_ TypeAndValue, err error) {
	// 解析表达式
	node, err := parser.ParseExprFrom(fset, "eval", expr, 0)
	if err != nil {
		return TypeAndValue{}, err
	}

	info := &Info{
		Types: make(map[ast.Expr]TypeAndValue),
	}
	err = CheckExpr(fset, pkg, pos, node, info)
	return info.Types[node], err
}

// CheckExpr类型检查表达式expr，就像它出现在
// 包装件的位置。键入有关表达式的信息
// 记录在信息中。
// None
// 如果pkg==nil，则使用宇宙范围并提供
// 位置pos被忽略。如果打包！=零，并且pos无效，
// 使用包范围。否则，pos必须属于
// 包裹
// None
// 如果pos不在包内或
// 如果无法对节点进行类型检查。
// None
// 注意：不应使用Eval和CheckExpr代替运行Check
// 计算类型和值，但除了检查，如下所示
// 函数忽略使用表达式的上下文（例如
// 分配）。因此，顶级非类型化常量将返回
// 非类型化类型，而不是相应的上下文特定类型。
// None
func CheckExpr(fset *token.FileSet, pkg *Package, pos token.Pos, expr ast.Expr, info *Info) (err error) {
	// 确定范围
	var scope *Scope
	if pkg == nil {
		scope = Universe
		pos = token.NoPos
	} else if !pos.IsValid() {
		scope = pkg.scope
	} else {
		// 包范围范围（位置信息）可以是
		// 不正确（文件分布在广泛的fset中
		// 位置-忽略它，只考虑它的孩子
		// （文件范围）。
		for _, fscope := range pkg.scope.children {
			if scope = fscope.Innermost(pos); scope != nil {
				break
			}
		}
		if scope == nil || debug {
			s := scope
			for s != nil && s != pkg.scope {
				s = s.parent
			}
			// s==nil | | s==pkg.scope
			if s == nil {
				return fmt.Errorf("no position %s found in package %s", fset.Position(pos), pkg.name)
			}
		}
	}

	// 初始化检查器
	check := NewChecker(nil, fset, pkg, info)
	check.scope = scope
	check.pos = pos
	defer check.handleBailout(&err)

	// 求值节点
	var x operand
	check.rawExpr(&x, expr, nil)
	check.processDelayed(0) // 包括所有功能
	check.recordUntyped()

	return nil
}
